package com.copro.simple;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.ihelpuoo.core.Nut;
import org.ihelpuoo.db.ConnectionPool;
import org.ihelpuoo.db.sql.SQLSegment;
import org.ihelpuoo.lang.InitializeException;
import org.ihelpuoo.lang.NutException;
import org.ihelpuoo.lang.ObjectUtils;
import org.ihelpuoo.localize.Localize;
import org.ihelpuoo.parser.segment.CharSegmentFactory;
import org.ihelpuoo.parser.segment.SegmentUtils;
import org.ihelpuoo.util.Pair;
import org.ihelpuoo.util.Properties;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.labels.StandardPieSectionLabelGenerator;
import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.Dataset;
import org.jfree.data.general.PieDataset;

import com.copro.chemdb.stat.ExportRule;
import com.copro.chemdb.stat.StatRule;
import com.copro.simple.db.FreeCallback;
import com.copro.simple.db.QueryCallback;
import com.copro.simple.db.SQLSet;

public class Application implements SQLSet {

	public static Application me(HttpServletRequest request) {
		return me(request.getSession().getServletContext());
	}

	public static Application me(HttpSession session) {
		return me(session.getServletContext());
	}

	public static Application me(ServletContext sc) {
		Application app = (Application) sc.getAttribute(Application.class
				.getName());
		if (null == app) {
			synchronized (sc) {
				app = (Application) sc
						.getAttribute(Application.class.getName());
				if (null == app) {
					app = new Application(Nut.me(sc));
					sc.setAttribute(Application.class.getName(), app);
				}
			}
		}
		return app;
	}

	private Application(Nut nut) {
		this.nut = nut;
		this.datetimeFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	}

	private Nut nut;
	private ConnectionPool pool;
	private List<Pair> subjectTypes;
	private List<Pair> statRuleNames;
	private Map<String, StatRule> statRules;
	private Map<String, ExportRule> exportRules;
	private CharSegmentFactory charSegmentFactory;
	private SimpleDateFormat datetimeFormat;
	private Map<Integer, byte[]> statCharts = new Hashtable<Integer, byte[]>();

	void init(String file) throws SimpleException {
		try {
			this.pool = (ConnectionPool) nut.getFactory(file);
			this.subjectTypes = buildPairList("SubjectType.properties");
			this.charSegmentFactory = (CharSegmentFactory) nut
					.getFactory("quesSeg.properties");
			buildRules();
		} catch (NutException e) {
			throw new SimpleException(e.getMessage());
		}
	}

	private void buildRules() throws InitializeException, SimpleException {
		this.statRuleNames = buildPairList("rules.properties");
		statRules = new HashMap<String, StatRule>();
		exportRules = new HashMap<String, ExportRule>();
		Iterator<Pair> it = statRuleNames.iterator();
		while (it.hasNext()) {
			Pair pair = it.next();
			String key = pair.getValueAsString();
			Properties conf = nut.getConfig(pair.getName());
			try {
				Class<?> klass = Class.forName(conf.getString("class"));
				Class<?> klassExport = Class.forName(conf
						.getString("classExport"));
				pair.setName(Localize.convertAscii2Native(conf
						.getString("text")));
				StatRule rule = (StatRule) klass.newInstance();
				rule.setup(conf);
				statRules.put(key, rule);
				ExportRule exportRule = (ExportRule) klassExport.newInstance();
				exportRule.setup(conf);
				exportRules.put(key, exportRule);
			} catch (Exception e) {
				throw new SimpleException(e.getMessage());
			}
		}
	}

	private List<Pair> buildPairList(String fileName)
			throws InitializeException {
		List<Pair> list = new LinkedList<Pair>();
		Properties p = nut.getConfig(fileName);
		Iterator<String> it = p.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			Pair pair = new Pair();
			pair.setName(Localize.convertAscii2Native(p.getString(key)));
			pair.setValue(key);
			list.add(pair);
		}
		Collections.sort(list, new PairComarator());
		return list;
	}

	void depose() {
		if (null != pool)
			pool.depose();
	}

	public StatRule getStatRule(String key) {
		return this.statRules.get(key);
	}

	public ExportRule getExportRule(String key) {
		return this.exportRules.get(key);
	}

	public String formatDatetime(long ms) {
		return datetimeFormat.format(new Date(ms));
	}

	public String formatDatetime(Date date) {
		return datetimeFormat.format(date);
	}

	public List<Pair> getSubjectTypes() {
		return subjectTypes;
	}

	public List<Pair> getStatRuleNames() {
		return statRuleNames;
	}

	public SQLSegment getSQL(String key) {
		return pool.sqls().get(key);
	}

	public void exec(FreeCallback callback) throws SimpleException {
		Connection conn = null;
		try {
			conn = pool.pick();
			conn.setAutoCommit(false);
			callback.exec(conn, this);
			conn.commit();
			conn.setAutoCommit(true);

		} catch (Exception e) {
			if (null != conn)
				try {
					conn.rollback();
				} catch (SQLException e1) {
					throw new SimpleException(e1.getMessage());
				}
			throw new SimpleException(e.getMessage());
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
	}

	public boolean exec(String sql) throws SimpleException {
		boolean re = false;
		Connection conn = null;
		Statement statement = null;
		try {
			conn = pool.pick();
			statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_UPDATABLE);
			re = statement.execute(sql);
		} catch (Exception e) {
			throw new SimpleException(e.getMessage());
		} finally {
			if (statement != null) {
				try {
					statement.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
		return re;
	}

	public void execBatchByKey(Object obj, String... keys)
			throws SimpleException {
		String[] ss = new String[keys.length];
		for (int i = 0; i < ss.length; i++) {
			SQLSegment sql = getSQL(keys[i]);
			SegmentUtils.fillByObject(sql, obj);
			ss[i] = sql.toString();
		}
		execBatch(ss);
	}

	public void execBatch(String... sqls) throws SimpleException {
		Connection conn = null;
		Statement statement = null;
		try {
			conn = pool.pick();
			conn.setAutoCommit(false);
			statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
					ResultSet.CONCUR_UPDATABLE);
			for (int i = 0; i < sqls.length; i++) {
				statement.execute(sqls[i]);
			}
			conn.commit();
			conn.setAutoCommit(true);
		} catch (Exception e) {
			if (null != conn)
				try {
					conn.rollback();
				} catch (SQLException e1) {
					throw new SimpleException(e1.getMessage());
				}
			throw new SimpleException(e.getMessage());
		} finally {
			if (statement != null) {
				try {
					statement.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
	}

	public Object insert(String sql, String maxSQLKey, Object obj, String field)
			throws SimpleException {
		Connection conn = null;
		Statement stm = null;
		try {
			conn = pool.pick();
			conn.setAutoCommit(false);
			stm = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_UPDATABLE);
			obj = insertWithoutCommit(sql, maxSQLKey, obj, field, stm);
			conn.commit();
			conn.setAutoCommit(true);
		} catch (Exception e) {
			if (null != conn)
				try {
					conn.rollback();
				} catch (SQLException e1) {
					throw new SimpleException(e1.getMessage());
				}
			throw new SimpleException(e.getMessage());
		} finally {
			if (stm != null) {
				try {
					stm.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
		return obj;
	}

	public Object insertWithoutCommit(String sql, String maxSQLKey, Object obj,
			String field, Statement statement) throws SQLException {
		statement.execute(sql);
		ResultSet rs = statement
				.executeQuery(this.getSQL(maxSQLKey).toString());
		if (rs.first()) {
			try {
				Method method = ObjectUtils.getSetter(obj.getClass(), field,
						int.class);
				method.invoke(obj, rs.getInt(1));
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return obj;
	}

	public Object insertByKey(String insertKey, String maxKey, Object obj,
			String field) throws SimpleException {
		SQLSegment sql = this.getSQL(insertKey);
		SegmentUtils.fillByObject(sql, obj);
		return insert(sql.toString(), maxKey, obj, field);
	}

	public Object update(String key, Object obj) throws SimpleException {
		SQLSegment sql = this.getSQL(key);
		SegmentUtils.fillByObject(sql, obj);
		this.exec(sql.toString());
		return obj;
	}

	public void query(String sql, QueryCallback callback)
			throws SimpleException {
		Connection conn = null;
		Statement statement = null;
		ResultSet rs = null;
		try {
			conn = pool.pick();
			statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_READ_ONLY);
			rs = statement.executeQuery(sql);
			callback.exec(rs);
		} catch (Exception e) {
			throw new SimpleException(e.getMessage());
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (statement != null) {
				try {
					statement.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
	}

	public int getCount(String sql) throws SimpleException {
		Connection conn = null;
		Statement statement = null;
		ResultSet rs = null;
		int count = -1;
		try {
			conn = pool.pick();
			statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_READ_ONLY);
			rs = statement.executeQuery(sql);
			if (rs.next()) {
				count = rs.getInt(1);
			}
		} catch (Exception e) {
			throw new SimpleException(e.getMessage());
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (statement != null) {
				try {
					statement.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new SimpleException(e.getMessage());
				}
			}
		}
		return count;
	}

	public CharSegmentFactory getCharSegmentFactory() {
		return charSegmentFactory;
	}

	/**
	 * used to create a statchart
	 * 
	 * @param id
	 * @param dataset
	 * @param type
	 *            0: pie. 1: bar
	 */
	public void createStatChart(int id, Dataset dataset, int type) {
		switch (type) {
		case 0:
			// create pie chart
			createPieStatChart(id, dataset);
			break;
		case 1:
			// create bar chart
			createBarStatChart(id, dataset);
			break;
		}
	}

	public void createBarStatChart(int id, Dataset dataset) {
		JFreeChart chart = ChartFactory.createBarChart("统计图", "", "",
				(CategoryDataset) dataset, PlotOrientation.HORIZONTAL, true,
				true, false);
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int rowNum = ((CategoryDataset) dataset).getRowCount();
		chart.getCategoryPlot().getRenderer().setBaseItemLabelGenerator(
				new StandardCategoryItemLabelGenerator("{0}: {2}", NumberFormat
						.getNumberInstance()));
		chart.getCategoryPlot().getRenderer().setBaseItemLabelsVisible(true);
		try {
			ChartUtilities.writeChartAsPNG(out, chart, 600, rowNum * 25 + 70);
		} catch (IOException e) {
		}
		byte[] result = out.toByteArray();
		statCharts.put(id, result);
	}

	public void createPieStatChart(int id, Dataset dataset) {
		JFreeChart chart = ChartFactory.createPieChart("统计图",
				(PieDataset) dataset, true, true, false);
		((PiePlot) chart.getPlot())
				.setLabelGenerator(new StandardPieSectionLabelGenerator(
						"{0}: {1} ({2})"));
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			ChartUtilities.writeChartAsPNG(out, chart, 600, 480);
		} catch (IOException e) {
		}
		byte[] result = out.toByteArray();
		statCharts.put(id, result);
	}

	/**
	 * get the stat chart data as byte.
	 * 
	 * @param id
	 * @return
	 */
	public byte[] getStatChart(int id) {
		return statCharts.get(id);
	}

}
